CAS,Compare And Swap,即比较并交换,整个AQS同步组件、Atomic原子类操作等等都是以CAS实现的,甚至ConcurrentHashMap在1.8的版本中也调整为了CAS+Synchronized,可以说CAS是整个JUC的基石。

对于并发控制而言,锁是一种悲观策略,会阻塞线程执行,而无锁是一种乐观策略,它会假设对资源的访问是没有冲突的,既然没有冲突就不需要等待,线程不需要阻塞。

CAS分析

在CAS中有三个参数,内存位置V、旧的预期值A、要更新的值B,当且仅当内存位置V的值等于旧的预期值A时才会将内存位置V的值修改为B,否则什么也不干,其伪代码如下:

1
2
3
4
5
6
if (this.value == A) {
this.value = B;
return true;
} else {
return false;
}

其实上面这段伪代码也不是线程安全的,如果两个线程分别都通过了this.value == A的判断,那么最终value的值就未知了。

JUC下的atomic类都是通过CAS来实现的,下面就以AtomicInteger为例来阐述CAS的操作,如下:

1
2
3
4
5
6
7
8
9
10
11
12
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
private volatile int value;

Unsafe是CAS的核心类,Java无法直接访问底层操作系统,而是通过本地方法来访问,但是JVM还是开了一个后门:Unsafe,它提供了硬件级别的原子操作。

valueOffset为变量值在内存中的偏移地址,unsafe就是通过偏移地址来得到数据的原值的。

value当前值,使用volatile修饰,保证多线程中看见的是同一个。

下面看下AtomicInteger中的addAndGet()方法:

1
2
3
4
5
6
7
8
9
10
11
12
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while (!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}

内部调用unsafe的getAndAddInt方法,在getAndAddInt方法中又调用了compareAndSwapInt方法,其具有四个参数,分别表示对象、对象的地址、预期值、修改值。

我们再看下AtomicInteger类中的一个方法:compareAndSet,顾名思义,如果当前值等于预期值,则以原子方式将该值设置为给定的更新值:

1
2
3
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

可以发现,compareAndSet底层调用的仍然是compareAndSwapInt方法。

再看一个方法getAndIncrement:

1
2
3
4
5
6
7
8
9
10
11
12
public final int getAndIncrement() {
for (; ;) {
int current = get();
int next = current + 1;
/*
为了确保当前线程可以发现其他线程对临界区数据的修改,这里value使用volatile进行修饰
*/
if (compareAndSet(current, next)) {
return current;
}
}
}

这里就是先获取value的值,再加1,然后进行CAS操作,如果在读取value值和加1的过程中value的值被其他线程改变了,那么CAS失败,一直循环到成功为止。

CAS的ABA问题

因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有变化就更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了,ABA问题的解决思路就是使用版本号,在变量前面加上版本号,每次变量更新的时候把版本号加1,那么A-B-A就会变成1A-2B-3A,从Java1.5开始的JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题,这个类的compareAndSet方法作用是先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。